/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.cadmiumxml;

import org.xml.sax.Attributes;
import org.xml.sax.SAXParseException;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.CadmiumException;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.FalseExit;
import fr.x9c.cadmium.kernel.Fatal;
import fr.x9c.cadmium.kernel.Value;

/**
 * This class is a wrapper around an Objective Caml SAX handler.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
final class SAXHandler extends org.xml.sax.helpers.DefaultHandler {

    /** Runner used for callbacks. */
    private final CodeRunner runner;

    /** Objective Caml SAX handler. */
    private final Block handler;

    /**
     * Constructs an handler from a code runner and an Objective Caml handler.
     * @param runner runner for callbacks - should not be <tt>null</tt>
     * @param handler actual SAX handler - should not be <tt>null</tt>
     */
    SAXHandler(final CodeRunner runner, final Value handler) {
        assert runner != null : "null runner";
        assert handler != null : "null handler";
        this.runner = runner;
        this.handler = handler.asBlock();
    } // end constructor(CodeRunner, Value)

    /**
     * {@inheritDoc}
     */
    @Override
    public void characters(final char[] ch, final int start, final int length) {
        final String s = new String(ch, start, length);
        try {
            this.runner.callback(this.handler.get(0),
                                 Value.createFromBlock(Block.createString(s)));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'characters(char[], int, int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void ignorableWhitespace(final char[] ch, final int start, final int length) {
        final String s = new String(ch, start, length);
        try {
            this.runner.callback(this.handler.get(1),
                                 Value.createFromBlock(Block.createString(s)));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'ignorableWhitespace(char[], int, int)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void startDocument() {
        try {
            this.runner.callback(this.handler.get(2), Value.UNIT);
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'startDocument()'

    /**
     * {@inheritDoc}
     */
    @Override
    public void endDocument() {
        try {
            this.runner.callback(this.handler.get(3), Value.UNIT);
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'endDocument() {'

    /**
     * {@inheritDoc}
     */
    @Override
    public void startElement(final String uri, final String localName,
                             final String qName, final Attributes attributes) {
        try {
            this.runner.callback(this.handler.get(4),
                                 Value.createFromBlock(Block.createString(uri)),
                                 Value.createFromBlock(Block.createString(localName)),
                                 Value.createFromBlock(Block.createString(qName)),
                                 createAttributeList(attributes));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'startElement(String, String, String, Attributes)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void endElement(final String uri, final String localName, final String qName) {
        try {
            this.runner.callback(this.handler.get(5),
                                 Value.createFromBlock(Block.createString(uri)),
                                 Value.createFromBlock(Block.createString(localName)),
                                 Value.createFromBlock(Block.createString(qName)));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'endElement(String, String, String)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void startPrefixMapping(final String prefix, final String uri) {
        try {
            this.runner.callback(this.handler.get(6),
                                 Value.createFromBlock(Block.createString(prefix)),
                                 Value.createFromBlock(Block.createString(uri)));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'startPrefixMapping(String, String)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void endPrefixMapping(final String prefix) {
        try {
            this.runner.callback(this.handler.get(7),
                                 Value.createFromBlock(Block.createString(prefix)));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'endPrefixMapping(String)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void error(final SAXParseException e) {
        try {
            this.runner.callback(this.handler.get(8), wrapParseException(e));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'error(SAXParseException)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void fatalError(final SAXParseException e) {
        try {
            this.runner.callback(this.handler.get(9), wrapParseException(e));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'fatalError(SAXParseException)'

    /**
     * {@inheritDoc}
     */
    @Override
    public void warning(final SAXParseException e) {
        try {
            this.runner.callback(this.handler.get(10), wrapParseException(e));
        } catch (final Fail.Exception fe) {
            throw new WrappedException(fe);
        } catch (final Fatal.Exception fe) {
            throw new WrappedException(fe);
        } catch (final FalseExit fe) {
            throw new WrappedException(fe);
        } catch (final CadmiumException fe) {
            throw new WrappedException(fe);
        } // end try/catch
    } // end method 'warning(SAXParseException)'

    /**
     * Creates an association list (of strings) from attributes.
     * @param attrs attributes to convert - should not be <tt>null</tt>
     * @return association list where each couple is of the form
     *         <i>(attr_name, attr_value)</i>
     */
    private static Value createAttributeList(final Attributes attrs) {
        assert attrs != null : "null attrs";
        Value res = Value.EMPTY_LIST;
        for (int i = attrs.getLength() - 1; i >= 0; i--) {
            final Block couple =
                Block.createBlock(0,
                                  Value.createFromBlock(Block.createString(attrs.getQName(i))),
                                  Value.createFromBlock(Block.createString(attrs.getValue(i))));
            final Block b = Block.createBlock(Block.TAG_CONS,
                                              Value.createFromBlock(couple),
                                              res);
            res = Value.createFromBlock(b);
        } // end for
        return res;
    } // end method 'createAttributeList(Attributes)'

    /**
     * Wraps a <tt>SAXParseException</tt> into a <tt>parse_error</tt>.
     * @param e exception to wrap - should not be <tt>null</tt>
     * @return <tt>parse_error</tt> containing the informations of the passed exception
     */
    private static Value wrapParseException(final SAXParseException e) {
        assert e != null : "null e";
        final Block res =
            Block.createBlock(0,
                              Value.createFromLong(e.getLineNumber()),
                              Value.createFromLong(e.getColumnNumber()),
                              Value.createFromBlock(Block.createString(e.getMessage())));
        return Value.createFromBlock(res);
    } // end method 'wrapParseException(SAXParseException)'

} // end class 'SAXHandler'
